﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace AsmResolver.PE.Win32Resources.Dfm
{
    public class ObjectBinaryToText2
    {
        BinaryWriter Writer;
        Reader Reader;

        bool comments = false;
        public ObjectBinaryToText2(Stream input, Stream output)
        {
            NestingLevel = 0;
            Reader = new Reader(input);
            Writer = new BinaryWriter(output);
        }

        public void DoConvert()
        {
            try
            {
                Reader.ReadSignature();
                ConvertObject();
            }
            catch
            {

            }
        }

        int NestingLevel;
        string ObjectName, PropName;
        void WriteIndent()
        {
            var Buf = new byte[] { 0x20, 0x20 };
            for (var i = 0; i < NestingLevel; i++)
                Writer.Write(Buf, 0, Buf.Length);
        }

        void WriteBytes(byte[] S)
        {
            Writer.Write(S, 0, S.Length);
        }
        void WriteEscapedTBytes(byte[] S)
        {
            var bytes = new byte[2];
            for (var i = 0; i < S.Length; i++)
            {
                switch (S[i])
                {
                    case 0:
                        bytes[0] = 0x5C;
                        bytes[1] = 0x30;
                        Writer.Write(bytes, 0, 2);
                        break;
                    case 9:
                        bytes[0] = 0x5C;
                        bytes[1] = 0x74;
                        Writer.Write(bytes, 0, 2);
                        break;
                    case 10:
                        bytes[0] = 0x5C;
                        bytes[1] = 0x6E;
                        Writer.Write(bytes, 0, 2);
                        break;
                    case 11:
                        bytes[0] = 0x5C;
                        bytes[1] = 0x76;
                        Writer.Write(bytes, 0, 2);
                        break;
                    case 12:
                        bytes[0] = 0x5C;
                        bytes[1] = 0x66;
                        Writer.Write(bytes, 0, 2);
                        break;
                    case 13:
                        bytes[0] = 0x5C;
                        bytes[1] = 0x72;
                        Writer.Write(bytes, 0, 2);
                        break;
                    case 92:
                        bytes[0] = 0x5C;
                        bytes[1] = 0x5C;
                        Writer.Write(bytes, 0, 2);
                        break;
                    default:
                        Writer.Write(S[i]);
                        break;
                }
            }
        }

        void WriteAsciiStr(string S)
        {
            var Buf = Encoding.ASCII.GetBytes(S);
            Writer.Write(Buf, 0, Buf.Length);
        }

        void WriteByte(byte B)
        {
            Writer.Write(B);
        }
        void WriteUTF8Str(string S)
        {
            var Buf = Encoding.UTF8.GetBytes(S);
            Writer.Write(Buf, 0, Buf.Length);
        }
        void WriteEscapedUTF8Str(string S)
        {
            var Buf = Encoding.UTF8.GetBytes(S);
            WriteEscapedTBytes(Buf);
        }

        void NewLine()
        {
            WriteAsciiStr(Environment.NewLine);
            WriteIndent();
        }

        void ConvertObject()
        {
            ConvertHeader();
            NestingLevel++;
            while (!Reader.EndOfList())
                ConvertProperty();
            Reader.ReadListEnd();
            while (!Reader.EndOfList())
                ConvertObject();
            Reader.ReadListEnd();
            NestingLevel--;
            WriteIndent();
            WriteAsciiStr("end" + Environment.NewLine);
        }

        private void ConvertProperty()
        {
            WriteIndent();
            PropName = Reader.ReadStr();
            WriteUTF8Str(PropName);
            WriteAsciiStr(" = ");
            ConvertValue();
            WriteAsciiStr(Environment.NewLine);
        }


        const int LineLength = 64;
        private void ConvertValue()
        {
            int I = 0;
            switch (Reader.NextValue())
            {
                case ValueType.vaList:
                    Reader.ReadValue();
                    WriteAsciiStr("(");
                    NestingLevel++;
                    while (!Reader.EndOfList())
                    {
                        NewLine();
                        ConvertValue();
                    }
                    Reader.ReadListEnd();
                    NestingLevel--;
                    WriteAsciiStr(")");
                    break;
                case ValueType.vaInt8:
                case ValueType.vaInt16:
                case ValueType.vaInt32:
                    WriteAsciiStr(Reader.ReadInteger().ToString());
                    break;
                case ValueType.vaExtended:
                case ValueType.vaDouble:
                    Reader.ReadFloat();
                    break;
                case ValueType.vaSingle:
                    Reader.ReadSingle();
                    break;
                case ValueType.vaCurrency:
                    Reader.ReadCurrency();
                    break;
                case ValueType.vaDate:
                    Reader.ReadDate();
                    break;
                case ValueType.vaWString:
                case ValueType.vaUTF8String:
                case ValueType.vaString:
                case ValueType.vaLString:
                    string W = Reader.ReadString();
                    int L = W.Length - 1;
                    if (W == "")
                    {
                        WriteAsciiStr("''");
                    }
                    else
                    {
                        I = 0;
                        NestingLevel++;
                        try
                        {
                            if (W.Length > LineLength)
                                NewLine();
                            int K = I;
                            do
                            {
                                bool LineBreak = false;
                                if (W[I] >= ' ' && W[I] != '\'' && W[I] <= 127)
                                {
                                    int J = I;
                                    do
                                    {
                                        I++;
                                    } while (!(I > L || W[I] < ' ' || W[I] == '\'' || (I - K >= LineLength) || W[I] > 127));
                                    if (I - K >= LineLength)
                                        LineBreak = true;
                                    WriteAsciiStr("'");
                                    while (J < I)
                                    {
                                        WriteByte((byte)W[J]);
                                        J++;
                                    }
                                    WriteAsciiStr("'");
                                }
                                else
                                {
                                    WriteAsciiStr("#");
                                    WriteAsciiStr(W[I].ToString());
                                    I++;
                                    if (I - K >= LineLength)
                                        LineBreak = true;
                                }
                                if (LineBreak && I <= L)
                                {
                                    WriteAsciiStr(" +");
                                    NewLine();
                                    K = I;
                                }
                            } while (!(I > L));
                        }
                        finally
                        {
                            NestingLevel--;
                        }
                    }
                    if (comments)
                    {
                        WriteAsciiStr(" //-");
                        WriteEscapedUTF8Str(W);
                        WriteAsciiStr(" -//");
                    }
                    break;
                case ValueType.vaIdent:
                case ValueType.vaFalse:
                case ValueType.vaTrue:
                case ValueType.vaNil:
                case ValueType.vaNull:
                    WriteUTF8Str(Reader.ReadIdent());
                    break;
                case ValueType.vaBinary:
                    ConvertBinary();
                    break;
                case ValueType.vaSet:
                    Reader.ReadValue();
                    WriteAsciiStr("[");
                    I = 0;
                    while (true)
                    {
                        var S = Reader.ReadStr();
                        if (S == "")
                            break;
                        if (I > 0)
                            WriteAsciiStr(", ");
                        WriteUTF8Str(S);
                        I++;
                    }
                    WriteAsciiStr("]");
                    break;
                case ValueType.vaCollection:
                    Reader.ReadValue();
                    WriteAsciiStr("<");
                    NestingLevel++;
                    while (!Reader.EndOfList())
                    {
                        NewLine();
                        WriteAsciiStr("item");
                        var t = Reader.NextValue();
                        if (t == ValueType.vaInt8 || t == ValueType.vaInt16 || t == ValueType.vaInt32)
                        {
                            WriteAsciiStr(" [");
                            ConvertValue();
                            WriteAsciiStr("]");
                        }
                        WriteAsciiStr(Environment.NewLine);
                        Reader.CheckValue(ValueType.vaList);
                        NestingLevel++;
                        while (!Reader.EndOfList())
                            ConvertProperty();
                        Reader.ReadListEnd();
                        NestingLevel--;
                        WriteIndent();
                        WriteAsciiStr("end");
                    }
                    Reader.ReadListEnd();
                    NestingLevel--;
                    WriteAsciiStr(">");
                    break;
                case ValueType.vaInt64:
                    WriteAsciiStr(Reader.ReadInt64().ToString());
                    break;
                default:
                    throw new Exception("ReadError");
            }
        }

        private void ConvertBinary()
        {
            const int BytesPerLine = 32;

            Reader.ReadValue();
            WriteAsciiStr("{");
            NestingLevel++;
            var Count = Reader.ReadInt32();
            var MultiLine = Count >= BytesPerLine;
            var I = 0;
            while (Count > 0)
            {
                if (MultiLine)
                    NewLine();
                if (Count >= 32)
                    I = 32;
                else
                    I = Count;
                var Buffer = Reader.ReadBytes(I);
                var Text = BinToHex(Buffer);
                WriteAsciiStr(Text);
                Count -= I;
            }
            NestingLevel--;
            WriteAsciiStr("}");
        }

        public static string BinToHex(byte[] bytes)
        {
            var result = "";
            for (var i = 0; i < bytes.Length; i++)
            {
                result += string.Format("{0:X2}", bytes[i]);
            }
            return result;
        }

        void ConvertHeader()
        {
            FilerFlag flags = 0;
            int position = 0;
            Reader.ReadPrefix(ref flags, ref position);
            var ClassName = Reader.ReadStr();
            ObjectName = Reader.ReadStr();
            WriteIndent();
            if ((flags & FilerFlag.ffInherited) == FilerFlag.ffInherited)
                WriteAsciiStr("inherited ");
            else if ((flags & FilerFlag.ffInline) == FilerFlag.ffInline)
                WriteAsciiStr("inline ");
            else
                WriteAsciiStr("object ");
            if (ObjectName != "")
            {
                WriteUTF8Str(ObjectName);
                WriteAsciiStr(": ");
            }
            WriteUTF8Str(ClassName);
            if ((flags & FilerFlag.ffChildPos) == FilerFlag.ffChildPos)
            {
                WriteAsciiStr(" [");
                WriteAsciiStr(position.ToString());
                WriteAsciiStr("]");
            }
            if (ObjectName == "")
                ObjectName = ClassName;

            WriteAsciiStr(Environment.NewLine);
        }
    }
}
